﻿using System;
using Pfz.Extensions;

namespace Pfz.Serialization
{
	/// <summary>
	/// A configurable binary serializer that does not comes pre-configured.
	/// </summary>
	public sealed class ConfigurableBinarySerializer:
		ConfigurableSerializerBase
	{
		/// <summary>
		/// Serializes null as byte 0.
		/// </summary>
		protected override void OnSerializeNull()
		{
			Stream.WriteByte(0);
		}

		/// <summary>
		/// Serializes a reference by writing a byte 1 and then the compressed id.
		/// </summary>
		protected override void OnSerializeReference(int id)
		{
			Stream.WriteByte(1);
			Stream.WriteCompressedInt32(id);
		}

		/// <summary>
		/// Serializes a type by its index (+2)
		/// </summary>
		protected override void OnSerializeType(int typeIndex, Type type, Type deserializerWillExpectType)
		{
			if (type.IsValueType && type == deserializerWillExpectType)
				return;

			Stream.WriteCompressedInt32(typeIndex+2);
		}

		/// <summary>
		/// Deserializes an object.
		/// </summary>
		protected override object OnDeserialize(Type expectedType)
		{
			Type effectiveType = null;
			if (expectedType != null && expectedType.IsValueType)
			{
				if (!(expectedType.IsGenericType && expectedType.GetGenericTypeDefinition() == typeof(Nullable<>)))
					effectiveType = expectedType;
			}

			if (effectiveType == null)
			{
				var stream = Stream;
				int value = stream.ReadCompressedInt32();

				switch(value)
				{
					case 0:
						return null;

					case 1:
					{
						int referenceId = stream.ReadCompressedInt32();
						return ResolveReference(referenceId);
					}
				}

				effectiveType = GetTypeById(value-2);
			}

			using(var serializationIdGenerator = CreateIdGenerator(effectiveType))
			{
				var itemDeserializerReference = GetItemSerializer(effectiveType);
				var itemDeserializer = itemDeserializerReference.ItemSerializer;

				object result = itemDeserializer.Deserialize(this);
				serializationIdGenerator.Result = result;
				return result;
			}
		}
	}
}
